Skip to content

feat(track): external correlator-output ingest for FPGA offload (#203)#207

Merged
zsoerenm merged 1 commit into
masterfrom
fix-203
Jul 24, 2026
Merged

feat(track): external correlator-output ingest for FPGA offload (#203)#207
zsoerenm merged 1 commit into
masterfrom
fix-203

Conversation

@zsoerenm

Copy link
Copy Markdown
Member

Closes #203.

Sanctions an external correlator producer (e.g. the LiteX M2SDR FPGA) feeding completed correlator dumps to the Doppler estimator, so downconversion + correlation can be offloaded to hardware while only the tracking loop filters run on the host. Builds on the per-chunk correlator_outputs mechanism from #205.

What changed

1. Estimator decoupled from BandMeasurements

estimate_dopplers_and_filter_prompt / estimate_dopplers_and_filter_prompt! (for ConventionalPLLAndDLL, hence also the default ConventionalAssistedPLLAndDLL) now accept a per-band sampling-frequency source — a NamedTuple/Dict keyed by band_key — in addition to BandMeasurements. The per-band rate is looked up through a new _band_sampling_frequency helper, so the estimator stays per-band (groups may sit on different bands) and needs no sample buffer. The BandMeasurements overload is preserved, so the CPU track! path is untouched and remains allocation-free (verified by the in-place per-stage allocation test).

2. External-producer ingest path

  • append_correlator_output! (exported): the blessed way to append an externally built CorrelatorOutput to a signal's buffer, at TrackedSignal / TrackedSat / TrackState addressing levels — cleaner and type-checked versus mutating the vector get_correlator_outputs returns.
  • get_correlator_outputs gains TrackedSat / TrackState accessors (per-signal addressing), matching the other accessors.
  • The "estimate consumes and clears the buffer" invariant is now documented as part of the public contract; running estimate_dopplers_and_filter_prompt!(track_state, fs) standalone over an externally-filled buffer skips downconvert_and_correlate! entirely.

3. sample_index convention + docs

  • CorrelatorOutput(correlator, integrated_samples, sample_index; code_phase = 0.0) keyword constructor for external producers.
  • New External correlator producers section in docs/src/track.md: the offload loop, the caller contract (raw accumulator scaling, true integrated_samples, per-signal sample_index order), how a free-running global sample counter maps onto the chunk-relative sample_index grid, and the one-epoch transport-delay note. The custom-estimator guide documents the fs-based method.

Testing

  • New test/external_correlator_producer.jl: exact equivalence between the BandMeasurements and fs paths, Dict/NamedTuple/immutable variants, the constructor, every append_correlator_output! addressing level, and a standalone external-producer estimate.
  • Full Pkg.test() passes; docs build cleanly with doctests; JuliaFormatter 2.8.5 clean.

Out of scope

DMA/FIFO ingest, transports, wire format, NCO marshaling, hardware channel management — all in GNSSReceiver.jl#107.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.24%. Comparing base (8f3b080) to head (aeea0e2).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #207      +/-   ##
==========================================
+ Coverage   98.12%   98.24%   +0.12%     
==========================================
  Files          32       32              
  Lines        3353     3365      +12     
==========================================
+ Hits         3290     3306      +16     
+ Misses         63       59       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (minimum time) — macos-14

Reporting the minimum over all samples (robust to shared-runner contention), not the median.

Alternative backends vs Float32 (track!, PR head)

Legend — backends: F32 Float32 (default) · I16 Int16 · 1b OneBit · 2b TwoBit (2-bit measurement + 2-bit carrier). Time columns are the minimum track! time; ×B = F32 / B (so >1 ⇒ backend B is faster than Float32), ✅ ≥ 5 % faster, ⚠️ ≥ 5 % slower. 1b/2b are BPSK-only, so their cells are blank for CBOC (Galileo E1B) scenarios.

Scenario F32 I16 1b 2b ×I16 ×1b ×2b
4-antenna @ 5 MHz 11.8 μs 6.24 μs 4.7 μs 9.75 μs 1.9 ✅ 2.52 ✅ 1.21 ✅
GPS L1CA, 8 sats @ 40 MHz 369.0 μs 117.0 μs 57.2 μs 109.0 μs 3.15 ✅ 6.44 ✅ 3.39 ✅
GPS L1CA, 8 sats @ 5 MHz, 100 ms buffer 5.06 ms 1.87 ms 1.19 ms 1.94 ms 2.71 ✅ 4.27 ✅ 2.61 ✅
GPS L1CA, 8 sats @ 5 MHz 53.8 μs 21.6 μs 15.5 μs 24.4 μs 2.49 ✅ 3.47 ✅ 2.2 ✅
Galileo E1B, 4 sats @ 25 MHz 142.0 μs 59.5 μs 2.38 ✅
dynamic taps @ 5 MHz (kernel) 6.68 μs 2.28 μs 1.49 μs 2.55 μs 2.93 ✅ 4.48 ✅ 2.62 ✅
multi-signal N=3 @ 5 MHz 11.2 μs 5.31 μs 4.47 μs 7.17 μs 2.12 ✅ 2.52 ✅ 1.57 ✅
Time benchmarks (base vs PR head)

Ratio = 8f3b080… / aeea0e2…: >1 means the PR is faster. ✅ ≥ 5 % faster, ⚠️ ≥ 5 % slower. A blank cell means the benchmark exists on only one revision (🆕 = new on the PR, 🗑 = removed).

8f3b080 aeea0e2 8f3b080… / aeea0e2
downconvert and correlate/CPU/Float32 2.71 μs 2.53 μs 1.07 ✅
downconvert and correlate/CPU/Float32 4ant 5.29 μs 4.93 μs 1.07 ✅
downconvert and correlate/CPU/Float64 2.95 μs 2.73 μs 1.08 ✅
downconvert and correlate/CPU/Int16 2.87 μs 2.67 μs 1.07 ✅
downconvert and correlate/CPU/Int16 4ant 5.24 μs 4.88 μs 1.07 ✅
downconvert and correlate/CPU/Int32 2.68 μs 2.58 μs 1.04
fused kernel/1-ant dynamic taps 2.57 μs 2.56 μs 1.01
fused kernel/1-ant static taps 2.06 μs 2.06 μs 1.0
fused kernel/4-ant dynamic taps 7.81 μs 7.78 μs 1.0
fused kernel/4-ant static taps 4.43 μs 4.43 μs 1.0
fused tuple kernel/1-ant N=2 3.39 μs 3.27 μs 1.04
fused tuple kernel/1-ant N=3 3.41 μs 3.4 μs 1.0
fused tuple kernel/2-ant N=2 6.32 μs 6.31 μs 1.0
fused tuple kernel/2-ant N=3 5.82 μs 5.62 μs 1.04
fused tuple kernel/4-ant N=2 12.0 μs 11.5 μs 1.04
fused tuple kernel/4-ant N=3 10.1 μs 10.1 μs 1.0
track/1. Float32/2K – track 2.94 μs 2.83 μs 1.04
track/1. Float32/2K – track! 3.11 μs 2.94 μs 1.06 ✅
track/2. L1 8sat/5K – track 54.8 μs 51.1 μs 1.07 ✅
track/2. L1 8sat/5K – track! 54.5 μs 50.9 μs 1.07 ✅
track/2. L1 8sat/5K – track! Int16 22.3 μs 23.9 μs 0.932 ⚠️
track/2. L1 8sat/5K – track! OneBit 17.2 μs 17.1 μs 1.0
track/2. L1 8sat/5K – track!-threaded 52.6 μs 52.6 μs 0.999
track/2. L1 8sat/5K – track-threaded 51.0 μs 51.0 μs 0.999
track/3. E1B 4sat/25K – track 136.0 μs 136.0 μs 1.0
track/3. E1B 4sat/25K – track! 136.0 μs 136.0 μs 1.0
track/3. E1B 4sat/25K – track! Int16 58.9 μs 59.2 μs 0.994
track/3. E1B 4sat/25K – track!-threaded 141.0 μs 146.0 μs 0.965
track/3. E1B 4sat/25K – track-threaded 137.0 μs 147.0 μs 0.932 ⚠️
track/4. 8L1+8E1B/25K – track 492.0 μs 527.0 μs 0.933 ⚠️
track/4. 8L1+8E1B/25K – track! 490.0 μs 490.0 μs 1.0
track/4. 8L1+8E1B/25K – track!-threaded 491.0 μs 491.0 μs 1.0
track/4. 8L1+8E1B/25K – track-threaded 492.0 μs 492.0 μs 1.0
track/5. multi-signal N=1/5K – track 6.87 μs 6.62 μs 1.04
track/5. multi-signal N=1/5K – track! 7.12 μs 6.59 μs 1.08 ✅
track/6. multi-signal N=2/5K – track 11.5 μs 10.7 μs 1.07 ✅
track/6. multi-signal N=2/5K – track! 11.6 μs 10.8 μs 1.08 ✅
track/7. L1 8sat/500K – track! Int16 1.91 ms 1.92 ms 0.997
track/7. L1 8sat/500K – track! OneBit 1.23 ms 1.23 ms 1.0
track/7. L1 8sat/500K – track! TwoBit 1.97 ms 2.14 ms 0.922 ⚠️
track/7. multi-signal N=3/5K – track 12.9 μs 12.0 μs 1.07 ✅
track/7. multi-signal N=3/5K – track! 12.1 μs 13.0 μs 0.93 ⚠️
track/8. L1CA presync 2 blk – track! 12.8 μs 13.7 μs 0.936 ⚠️
track/8. L1CA presync 20 blk – track! 127.0 μs 125.0 μs 1.02
track/8. L1CA synced 2 blk – track! 12.7 μs 12.7 μs 1.0
track/8. L1CA synced 20 blk – track! 122.0 μs 122.0 μs 1.0
time_to_load 145.0 μs 144.0 μs 1.01
Memory benchmarks (base vs PR head)

Ratio = 8f3b080… / aeea0e2… (bytes allocated): >1 means the PR allocates less. ✅ ≥ 5 % less, ⚠️ ≥ 5 % more. /0 mark a benchmark that drops to / picks up allocations, means both revisions allocate nothing. A blank cell means the benchmark exists on only one revision (🆕 = new on the PR, 🗑 = removed).

8f3b080 aeea0e2 8f3b080… / aeea0e2
downconvert and correlate/CPU/Float32 2 allocs: 576 B 2 allocs: 576 B 1.0
downconvert and correlate/CPU/Float32 4ant 2 allocs: 928 B 2 allocs: 928 B 1.0
downconvert and correlate/CPU/Float64 2 allocs: 576 B 2 allocs: 576 B 1.0
downconvert and correlate/CPU/Int16 2 allocs: 576 B 2 allocs: 576 B 1.0
downconvert and correlate/CPU/Int16 4ant 2 allocs: 928 B 2 allocs: 928 B 1.0
downconvert and correlate/CPU/Int32 2 allocs: 576 B 2 allocs: 576 B 1.0
fused kernel/1-ant dynamic taps 0 allocs: 0 B 0 allocs: 0 B
fused kernel/1-ant static taps 0 allocs: 0 B 0 allocs: 0 B
fused kernel/4-ant dynamic taps 0 allocs: 0 B 0 allocs: 0 B
fused kernel/4-ant static taps 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/1-ant N=2 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/1-ant N=3 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/2-ant N=2 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/2-ant N=3 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/4-ant N=2 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/4-ant N=3 0 allocs: 0 B 0 allocs: 0 B
track/1. Float32/2K – track 9 allocs: 944 B 9 allocs: 944 B 1.0
track/1. Float32/2K – track! 0 allocs: 0 B 0 allocs: 0 B
track/2. L1 8sat/5K – track 10 allocs: 4656 B 10 allocs: 4656 B 1.0
track/2. L1 8sat/5K – track! 0 allocs: 0 B 0 allocs: 0 B
track/2. L1 8sat/5K – track! Int16 13 allocs: 1056 B 13 allocs: 1056 B 1.0
track/2. L1 8sat/5K – track! OneBit 45 allocs: 2736 B 45 allocs: 2736 B 1.0
track/2. L1 8sat/5K – track!-threaded 0 allocs: 0 B 0 allocs: 0 B
track/2. L1 8sat/5K – track-threaded 10 allocs: 4656 B 10 allocs: 4656 B 1.0
track/3. E1B 4sat/25K – track 10 allocs: 3056 B 10 allocs: 3056 B 1.0
track/3. E1B 4sat/25K – track! 0 allocs: 0 B 0 allocs: 0 B
track/3. E1B 4sat/25K – track! Int16 5 allocs: 416 B 5 allocs: 416 B 1.0
track/3. E1B 4sat/25K – track!-threaded 0 allocs: 0 B 0 allocs: 0 B
track/3. E1B 4sat/25K – track-threaded 10 allocs: 3056 B 10 allocs: 3056 B 1.0
track/4. 8L1+8E1B/25K – track 26 allocs: 10976 B 26 allocs: 10976 B 1.0
track/4. 8L1+8E1B/25K – track! 0 allocs: 0 B 0 allocs: 0 B
track/4. 8L1+8E1B/25K – track!-threaded 0 allocs: 0 B 0 allocs: 0 B
track/4. 8L1+8E1B/25K – track-threaded 26 allocs: 10976 B 26 allocs: 10976 B 1.0
track/5. multi-signal N=1/5K – track 9 allocs: 944 B 9 allocs: 944 B 1.0
track/5. multi-signal N=1/5K – track! 0 allocs: 0 B 0 allocs: 0 B
track/6. multi-signal N=2/5K – track 9 allocs: 1408 B 9 allocs: 1408 B 1.0
track/6. multi-signal N=2/5K – track! 0 allocs: 0 B 0 allocs: 0 B
track/7. L1 8sat/500K – track! Int16 61 allocs: 28704 B 61 allocs: 28704 B 1.0
track/7. L1 8sat/500K – track! OneBit 1677 allocs: 119088 B 1677 allocs: 119088 B 1.0
track/7. L1 8sat/500K – track! TwoBit 1677 allocs: 119136 B 1677 allocs: 119136 B 1.0
track/7. multi-signal N=3/5K – track 9 allocs: 1760 B 9 allocs: 1760 B 1.0
track/7. multi-signal N=3/5K – track! 0 allocs: 0 B 0 allocs: 0 B
track/8. L1CA presync 2 blk – track! 7 allocs: 320 B 7 allocs: 320 B 1.0
track/8. L1CA presync 20 blk – track! 7 allocs: 320 B 7 allocs: 320 B 1.0
track/8. L1CA synced 2 blk – track! 7 allocs: 320 B 7 allocs: 320 B 1.0
track/8. L1CA synced 20 blk – track! 7 allocs: 320 B 7 allocs: 320 B 1.0
time_to_load 196 allocs: 13984 B 196 allocs: 13984 B 1.0

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results (minimum time) — ubuntu-latest

Reporting the minimum over all samples (robust to shared-runner contention), not the median.

Alternative backends vs Float32 (track!, PR head)

Legend — backends: F32 Float32 (default) · I16 Int16 · 1b OneBit · 2b TwoBit (2-bit measurement + 2-bit carrier). Time columns are the minimum track! time; ×B = F32 / B (so >1 ⇒ backend B is faster than Float32), ✅ ≥ 5 % faster, ⚠️ ≥ 5 % slower. 1b/2b are BPSK-only, so their cells are blank for CBOC (Galileo E1B) scenarios.

Scenario F32 I16 1b 2b ×I16 ×1b ×2b
4-antenna @ 5 MHz 12.0 μs 7.48 μs 5.84 μs 11.0 μs 1.61 ✅ 2.06 ✅ 1.09 ✅
GPS L1CA, 8 sats @ 40 MHz 338.0 μs 139.0 μs 63.7 μs 117.0 μs 2.43 ✅ 5.31 ✅ 2.9 ✅
GPS L1CA, 8 sats @ 5 MHz, 100 ms buffer 4.77 ms 2.38 ms 1.48 ms 2.4 ms 2.0 ✅ 3.23 ✅ 1.99 ✅
GPS L1CA, 8 sats @ 5 MHz 51.7 μs 28.4 μs 19.0 μs 27.3 μs 1.82 ✅ 2.73 ✅ 1.89 ✅
Galileo E1B, 4 sats @ 25 MHz 126.0 μs 60.1 μs 2.1 ✅
dynamic taps @ 5 MHz (kernel) 6.35 μs 3.57 μs 1.78 μs 2.67 μs 1.78 ✅ 3.56 ✅ 2.38 ✅
multi-signal N=3 @ 5 MHz 10.9 μs 6.49 μs 5.77 μs 8.34 μs 1.67 ✅ 1.88 ✅ 1.3 ✅
Time benchmarks (base vs PR head)

Ratio = 8f3b080… / aeea0e2…: >1 means the PR is faster. ✅ ≥ 5 % faster, ⚠️ ≥ 5 % slower. A blank cell means the benchmark exists on only one revision (🆕 = new on the PR, 🗑 = removed).

8f3b080 aeea0e2 8f3b080… / aeea0e2
downconvert and correlate/CPU/Float32 2.43 μs 2.42 μs 1.0
downconvert and correlate/CPU/Float32 4ant 4.47 μs 4.46 μs 1.0
downconvert and correlate/CPU/Float64 2.81 μs 2.81 μs 0.998
downconvert and correlate/CPU/Int16 2.53 μs 2.53 μs 0.999
downconvert and correlate/CPU/Int16 4ant 4.91 μs 4.9 μs 1.0
downconvert and correlate/CPU/Int32 2.46 μs 2.45 μs 1.0
fused kernel/1-ant dynamic taps 2.3 μs 2.29 μs 1.0
fused kernel/1-ant static taps 1.91 μs 1.92 μs 0.997
fused kernel/4-ant dynamic taps 5.56 μs 5.6 μs 0.994
fused kernel/4-ant static taps 3.88 μs 3.87 μs 1.0
fused tuple kernel/1-ant N=2 2.57 μs 2.57 μs 1.0
fused tuple kernel/1-ant N=3 3.15 μs 3.09 μs 1.02
fused tuple kernel/2-ant N=2 3.95 μs 3.94 μs 1.0
fused tuple kernel/2-ant N=3 5.05 μs 4.99 μs 1.01
fused tuple kernel/4-ant N=2 6.47 μs 6.48 μs 0.999
fused tuple kernel/4-ant N=3 10.2 μs 10.2 μs 1.0
track/1. Float32/2K – track 2.81 μs 2.83 μs 0.994
track/1. Float32/2K – track! 2.88 μs 2.91 μs 0.992
track/2. L1 8sat/5K – track 49.7 μs 49.7 μs 1.0
track/2. L1 8sat/5K – track! 48.9 μs 49.5 μs 0.987
track/2. L1 8sat/5K – track! Int16 28.4 μs 30.0 μs 0.948 ⚠️
track/2. L1 8sat/5K – track! OneBit 19.1 μs 19.0 μs 1.0
track/2. L1 8sat/5K – track!-threaded 49.3 μs 49.2 μs 1.0
track/2. L1 8sat/5K – track-threaded 49.6 μs 49.8 μs 0.995
track/3. E1B 4sat/25K – track 122.0 μs 122.0 μs 0.999
track/3. E1B 4sat/25K – track! 121.0 μs 121.0 μs 1.0
track/3. E1B 4sat/25K – track! Int16 59.0 μs 58.9 μs 1.0
track/3. E1B 4sat/25K – track!-threaded 122.0 μs 122.0 μs 0.999
track/3. E1B 4sat/25K – track-threaded 122.0 μs 122.0 μs 1.0
track/4. 8L1+8E1B/25K – track 446.0 μs 445.0 μs 1.0
track/4. 8L1+8E1B/25K – track! 444.0 μs 444.0 μs 1.0
track/4. 8L1+8E1B/25K – track!-threaded 444.0 μs 443.0 μs 1.0
track/4. 8L1+8E1B/25K – track-threaded 445.0 μs 445.0 μs 1.0
track/5. multi-signal N=1/5K – track 6.41 μs 6.4 μs 1.0
track/5. multi-signal N=1/5K – track! 6.58 μs 6.44 μs 1.02
track/6. multi-signal N=2/5K – track 9.46 μs 9.36 μs 1.01
track/6. multi-signal N=2/5K – track! 9.64 μs 9.59 μs 1.01
track/7. L1 8sat/500K – track! Int16 2.35 ms 2.36 ms 0.994
track/7. L1 8sat/500K – track! OneBit 1.45 ms 1.45 ms 0.997
track/7. L1 8sat/500K – track! TwoBit 2.2 ms 2.44 ms 0.903 ⚠️
track/7. multi-signal N=3/5K – track 12.1 μs 12.2 μs 0.993
track/7. multi-signal N=3/5K – track! 12.3 μs 12.4 μs 0.992
track/8. L1CA presync 2 blk – track! 12.0 μs 12.1 μs 0.995
track/8. L1CA presync 20 blk – track! 116.0 μs 115.0 μs 1.0
track/8. L1CA synced 2 blk – track! 11.9 μs 12.0 μs 0.995
track/8. L1CA synced 20 blk – track! 114.0 μs 114.0 μs 1.0
time_to_load 118.0 μs 115.0 μs 1.02
Memory benchmarks (base vs PR head)

Ratio = 8f3b080… / aeea0e2… (bytes allocated): >1 means the PR allocates less. ✅ ≥ 5 % less, ⚠️ ≥ 5 % more. /0 mark a benchmark that drops to / picks up allocations, means both revisions allocate nothing. A blank cell means the benchmark exists on only one revision (🆕 = new on the PR, 🗑 = removed).

8f3b080 aeea0e2 8f3b080… / aeea0e2
downconvert and correlate/CPU/Float32 2 allocs: 576 B 2 allocs: 576 B 1.0
downconvert and correlate/CPU/Float32 4ant 2 allocs: 928 B 2 allocs: 928 B 1.0
downconvert and correlate/CPU/Float64 2 allocs: 576 B 2 allocs: 576 B 1.0
downconvert and correlate/CPU/Int16 2 allocs: 576 B 2 allocs: 576 B 1.0
downconvert and correlate/CPU/Int16 4ant 2 allocs: 928 B 2 allocs: 928 B 1.0
downconvert and correlate/CPU/Int32 2 allocs: 576 B 2 allocs: 576 B 1.0
fused kernel/1-ant dynamic taps 0 allocs: 0 B 0 allocs: 0 B
fused kernel/1-ant static taps 0 allocs: 0 B 0 allocs: 0 B
fused kernel/4-ant dynamic taps 0 allocs: 0 B 0 allocs: 0 B
fused kernel/4-ant static taps 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/1-ant N=2 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/1-ant N=3 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/2-ant N=2 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/2-ant N=3 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/4-ant N=2 0 allocs: 0 B 0 allocs: 0 B
fused tuple kernel/4-ant N=3 0 allocs: 0 B 0 allocs: 0 B
track/1. Float32/2K – track 9 allocs: 944 B 9 allocs: 944 B 1.0
track/1. Float32/2K – track! 0 allocs: 0 B 0 allocs: 0 B
track/2. L1 8sat/5K – track 10 allocs: 4664 B 10 allocs: 4664 B 1.0
track/2. L1 8sat/5K – track! 0 allocs: 0 B 0 allocs: 0 B
track/2. L1 8sat/5K – track! Int16 13 allocs: 1056 B 13 allocs: 1056 B 1.0
track/2. L1 8sat/5K – track! OneBit 45 allocs: 2736 B 45 allocs: 2736 B 1.0
track/2. L1 8sat/5K – track!-threaded 0 allocs: 0 B 0 allocs: 0 B
track/2. L1 8sat/5K – track-threaded 10 allocs: 4664 B 10 allocs: 4664 B 1.0
track/3. E1B 4sat/25K – track 10 allocs: 2872 B 10 allocs: 2872 B 1.0
track/3. E1B 4sat/25K – track! 0 allocs: 0 B 0 allocs: 0 B
track/3. E1B 4sat/25K – track! Int16 5 allocs: 416 B 5 allocs: 416 B 1.0
track/3. E1B 4sat/25K – track!-threaded 0 allocs: 0 B 0 allocs: 0 B
track/3. E1B 4sat/25K – track-threaded 10 allocs: 2872 B 10 allocs: 2872 B 1.0
track/4. 8L1+8E1B/25K – track 26 allocs: 10608 B 26 allocs: 10608 B 1.0
track/4. 8L1+8E1B/25K – track! 0 allocs: 0 B 0 allocs: 0 B
track/4. 8L1+8E1B/25K – track!-threaded 0 allocs: 0 B 0 allocs: 0 B
track/4. 8L1+8E1B/25K – track-threaded 26 allocs: 10608 B 26 allocs: 10608 B 1.0
track/5. multi-signal N=1/5K – track 9 allocs: 944 B 9 allocs: 944 B 1.0
track/5. multi-signal N=1/5K – track! 0 allocs: 0 B 0 allocs: 0 B
track/6. multi-signal N=2/5K – track 9 allocs: 1408 B 9 allocs: 1408 B 1.0
track/6. multi-signal N=2/5K – track! 0 allocs: 0 B 0 allocs: 0 B
track/7. L1 8sat/500K – track! Int16 61 allocs: 25696 B 61 allocs: 25696 B 1.0
track/7. L1 8sat/500K – track! OneBit 1677 allocs: 116080 B 1677 allocs: 116080 B 1.0
track/7. L1 8sat/500K – track! TwoBit 1677 allocs: 116128 B 1677 allocs: 116128 B 1.0
track/7. multi-signal N=3/5K – track 9 allocs: 1760 B 9 allocs: 1760 B 1.0
track/7. multi-signal N=3/5K – track! 0 allocs: 0 B 0 allocs: 0 B
track/8. L1CA presync 2 blk – track! 7 allocs: 320 B 7 allocs: 320 B 1.0
track/8. L1CA presync 20 blk – track! 7 allocs: 320 B 7 allocs: 320 B 1.0
track/8. L1CA synced 2 blk – track! 7 allocs: 320 B 7 allocs: 320 B 1.0
track/8. L1CA synced 20 blk – track! 7 allocs: 320 B 7 allocs: 320 B 1.0
time_to_load 145 allocs: 11216 B 145 allocs: 11216 B 1.0

Sanction feeding externally produced `CorrelatorOutput`s (e.g. from an
FPGA/hardware correlator) to the Doppler estimator, running only the loop
filters on the host.

- Decouple `estimate_dopplers_and_filter_prompt(!)` from `BandMeasurements`:
  both the immutable and in-place `ConventionalPLLAndDLL` methods now accept a
  per-band sampling-frequency source (a `NamedTuple`/`Dict` keyed by
  `get_band_id`) as well as `BandMeasurements`. The estimator reads only the
  per-band rate (via the new `_band_sampling_frequency` helper), so the FPGA
  path needs no sample buffer. The CPU `track!` path is unchanged and stays
  allocation-free.
- Add `append_correlator_output!` (exported) — the blessed ingest path onto a
  signal's `correlator_outputs` buffer, at `TrackedSignal`/`TrackedSat`/
  `TrackState` addressing levels; add matching `get_correlator_outputs`
  accessors at the `TrackedSat`/`TrackState` levels.
- Document the `sample_index` convention for a free-running global counter,
  the "estimate consumes and clears the buffer" contract, the caller contract,
  and the one-epoch transport-delay note under a new "External correlator
  producers" section (docs/src/track.md), plus the fs-based method in the
  custom-estimator guide.
- Test: exact equivalence between the `BandMeasurements` and fs paths, the
  Dict/NamedTuple/immutable variants, every append addressing level, and a
  standalone external-producer estimate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zsoerenm
zsoerenm merged commit 8b03d86 into master Jul 24, 2026
12 checks passed
@zsoerenm
zsoerenm deleted the fix-203 branch July 24, 2026 06:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FPGA/hardware-correlator injection seam: feed external CorrelatorOutputs to the estimator

1 participant